/*************************************************************************************
 * Nombre del archivo: Automata_Finito.java
 * Universidad del Valle de Guatemala
 * Curso: Diseño de Lenguajes de Programación
 * @author martin
 *          Martín Guzmán 08041
 * Fecha: Marzo de 2010
 * Descripción: 
*************************************************************************************/
import java.io.File;
import java.io.FileWriter;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;

public abstract class Automata_Finito {

    protected LinkedList<Estado> Q = new LinkedList<Estado>();//lista de estados
    protected LinkedList<Estado> F = new LinkedList<Estado>();//lista de estados de aceptación
    protected LinkedList<Character> Sigma = new LinkedList<Character>();//lista de símbolos
    protected Estado q0;//estado inicial
    protected LinkedList<Transicion> delta = new LinkedList<Transicion>();//lista de transiciones
    protected String regex="";
    
    public Automata_Finito(){}

    /**
     * @return the Q
     */
    public LinkedList<Estado> getQ() {
        return Q;
    }

    /**
     * @param Q the Q to set
     */
    public void setQ(LinkedList<Estado> Q) {
        this.Q = Q;
    }

    /**
     * @return the F
     */
    public LinkedList<Estado> getF() {
        return F;
    }

    /**
     * @param F the F to set
     */
    public void setF(LinkedList<Estado> F) {
        this.F = F;
    }

    /**
     * @return the Sigma
     */
    public LinkedList<Character> getSigma() {
        return Sigma;
    }

    /**
     * @param Sigma the Sigma to set
     */
    public void setSigma(LinkedList<Character> Sigma) {
        this.Sigma = Sigma;
    }

    /**
     * @return the q0
     */
    public Estado getQ0() {
        return q0;
    }

    /**
     * @param q0 the q0 to set
     */
    public void setQ0(Estado q0) {
        this.q0 = q0;
    }

    /**
     * @return the delta
     */
    public LinkedList<Transicion> getDelta() {
        return delta;
    }

    /**
     * @param delta the delta to set
     */
    public void setDelta(LinkedList<Transicion> delta) {
        this.delta = delta;
    }

    /***********************************************
     * Método: comparar
     * Uso: compara si 2 cadenas son iguales
     * @return booleano que contiene la igualdad
    ***********************************************/
    public boolean comparar(String cadena1, String cadena2){
        if(cadena1.compareTo(cadena2)==0)
            return true;
        return false;
    }


    /***********************************************
     * Método: ordenar_transiciones
     * Uso: Crea un comparator para ordenar las
     *      transiciones
     * @return void
    ***********************************************/
    public void ordenar_transiciones(){

        Collections.sort(delta, new Comparator<Transicion>() {
            public int compare(Transicion o1, Transicion o2) {
                Integer a1 = Integer.parseInt(o1.getDesde().getNombre().substring(1));
                Integer a2 = Integer.parseInt(o2.getDesde().getNombre().substring(1));
                return a1-a2;
            }
        });

    }

    /***********************************************
     * Método: ordenar_estados
     * Uso: Crea un comparator para ordenar los
     *      estados
     * @return void
    ***********************************************/
    public void ordenar_estados(){

        Collections.sort(this.Q, new Comparator<Estado>() {
            public int compare(Estado o1, Estado o2) {
                Integer a1 = Integer.parseInt(o1.getNombre().substring(1));
                Integer a2 = Integer.parseInt(o2.getNombre().substring(1));
                return a1-a2;
            }
        });

    }


    /***********************************************
    * Método: Buscar_estado
    * Uso: busca y retorna un estado
    * @return Estado encontrado
    ***********************************************/
    public Estado Buscar_estado(String estado_txt){

        for(int i=0;i<this.Q.size();i++){
            if(this.comparar(Q.get(i).getNombre(), estado_txt)){
                return Q.get(i);
            }
        }
        return null;
    }

    /***********************************************
    * Método: Buscar_transicion
    * Uso: busca entre la lista de transiciones
     *      la transición que se desea
    * @return Integer con el índice en la
     *      lista de transiciones
    ***********************************************/
    public Integer Buscar_transicion(String Estado_fuente, Character caracter) {
        String retorno="";
        for(int i=0;i<this.delta.size();i++){

            if(this.comparar(this.delta.get(i).getDesde().getNombre(), Estado_fuente) && this.delta.get(i).getTransicion()==caracter){
                return i;
            }
        }
        return -1;
    }


    /***********************************************
    * Método: Obtener_Transiciones
    * Uso: Obtiene una lista de transiciones
    * @return LinkedList<Transicion> con las transiciones
    ***********************************************/
    public LinkedList<Transicion> Obtener_Transiciones(String Estado_fuente) {
        LinkedList<Transicion> retorno= new LinkedList<Transicion>();

        for(int i=0;i<this.delta.size();i++){
            if(this.comparar(this.delta.get(i).getDesde().getNombre(), Estado_fuente)){
                retorno.add(this.delta.get(i));
            }
        }
        return retorno;
    }

    /***********************************************
    * Método: Obtener_Transiciones
    * Uso: Obtiene una lista de transiciones
    * @return LinkedList<Transicion> con las transiciones
    ***********************************************/
    public LinkedList<Transicion> Obtener_Transiciones_epsilon(String Estado_fuente) {
        LinkedList<Transicion> retorno= new LinkedList<Transicion>();

        for(int i=0;i<this.delta.size();i++){
            if(this.comparar(this.delta.get(i).getDesde().getNombre(), Estado_fuente) && this.delta.get(i).getTransicion()==Main.epsilon){
                retorno.add(this.delta.get(i));
            }
        }
        return retorno;
    }

    /***********************************************
    * Método: Get_estados_se_puede_llegar_epsilon
    * Uso: Obtiene una lista de transiciones
    * @return LinkedList<Estado> con las transiciones
    ***********************************************/
    public LinkedList<Integer> Get_estados_se_puede_llegar_epsilon(String Estado_fuente) {
        
        LinkedList<Integer> retorno= new LinkedList<Integer>();

        retorno.add(Integer.parseInt(Estado_fuente.substring(1)));

        for(int i=0;i<this.delta.size();i++){
            if(this.comparar(this.delta.get(i).getDesde().getNombre(), Estado_fuente) && this.delta.get(i).getTransicion()==Main.epsilon){
                retorno.add(Integer.parseInt(this.delta.get(i).getHacia().getNombre().substring(1)));
            }
        }
        this.Sort_Set(retorno);
        
        return retorno;
    }

    /***********************************************
    * Método: Get_estados_se_puede_llegar_epsilon
    * Uso: Obtiene una lista de transiciones
    * @return LinkedList<Estado> con las transiciones
    ***********************************************/
    public LinkedList<Integer> mueve(String Estado_fuente, Character caracter) {
        //this.Print(Estado_fuente+" "+caracter);
        LinkedList<Integer> retorno= new LinkedList<Integer>();

        if(caracter == Main.epsilon){
            retorno.add(Integer.parseInt(Estado_fuente.substring(1)));
        }

        for(int i=0;i<this.delta.size();i++){
            if(this.comparar(this.delta.get(i).getDesde().getNombre(), Estado_fuente) && this.delta.get(i).getTransicion()==caracter){
                retorno.add(Integer.parseInt(this.delta.get(i).getHacia().getNombre().substring(1)));
            }
        }
        this.Sort_Set(retorno);

        return retorno;
    }

     /***********************************************
     * Método: agregar_a_lista
     * Uso: Agrega los elementos de lista_fuente a
     *      lista_destino
     *      contiene primera_pos del nodo especificado
     * @return LinkedList<Integer> con los elementos
    ***********************************************/
    public void agregar_a_lista(LinkedList<Integer> lista_fuente, LinkedList<Integer> lista_destino){
        for(int i=0;i<lista_fuente.size();i++){
            lista_destino.add(lista_fuente.get(i));
        }
    }

     /***********************************************
     * Método: agregar_a_lista
     * Uso: Agrega los elementos de lista_fuente a
     *      lista_destino
     *      contiene primera_pos del nodo especificado
     * @return LinkedList<Integer> con los elementos
    ***********************************************/
    public void agregar_a_lista_2(LinkedList lista_fuente, LinkedList lista_destino){
        for(int i=0;i<lista_fuente.size();i++){
            lista_destino.add(lista_fuente.get(i));
        }
    }
    
     /***********************************************
    * Método: Sort_Set
    * Uso: Ordena y elimina los repetidos en una lista
    *      de números
    * @return void
    ***********************************************/
    public void Sort_Set(LinkedList<Integer> lista){
        Collections.sort(lista);
        //elimina los elementos repetidos ya que están ya ordenados
        for(int i=1;i<lista.size();i++){
            int numero1 = lista.get(i);
            int numero2 = lista.get(i-1);
            if(numero1==numero2){
                lista.remove(i--);
            }
        }
    }

     /***********************************************
    * Método: Print
    * Uso: Imprime el contenido de cadena
    * @param cadena con la cadena a imprimir
    * @return void
    ***********************************************/
    public void Print(String cadena){
        System.out.println(cadena);
    }

    /***********************************************
    * Método: Generar_Sage
    * Uso: Genera el código en sage para dibujar un
    *      digrafo que represente al autómata finito
    * @return void
    ***********************************************/
    public void Generar_Sage() {
        String cadena = "g = DiGraph({}, loops=True, multiedges=True,sparse=True)"+"\n";
        cadena+="g.add_edges([";

        cadena+=this.delta.get(0).Sage_String();
        for(int i=1;i<this.delta.size();i++){
            cadena+=","+this.delta.get(i).Sage_String();
        }

        cadena+="])"+"\n";
        cadena+="g.plot(edge_labels=True, color_by_label=True, edge_style='dashed')";

        System.out.println(cadena);

    }

    /***********************************************
    * Método: Generar_JFLAP
    * Uso: Genera el código en un xml que JFLAP entiende
    *      como un autómata y lo dibuja
    * @return void
    ***********************************************/
    public void Generar_JFLAP(){

        String texto = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n";
        texto+="<structure>"+"\n";
        texto+="<type>fa</type>"+"\n";
        texto+="\t<automaton>"+"\n";

        texto+="\t\t"+"<!--Lista de estados.-->"+"\n";
        /*
         <state id="0" name="q0">
            <x>94.0</x>
            <y>109.0</y>
            <initial/>
	</state>
         */
        for(int i=0;i<this.Q.size();i++){
            texto+="\t\t"+"<state id=\""+i+"\" name=\""+this.Q.get(i).getNombre()+"\">"+"\n";
            texto+="\t\t\t"+"<x>"+0+"</x>"+"\n";
            texto+="\t\t\t"+"<y>"+0+"</y>"+"\n";
            //revisar si es estado inicial
            if(this.q0==this.Q.get(i)){
                texto+="\t\t\t"+"<initial/>"+"\n";
            }
            //revisar si es estado de aceptación
            if(this.F.contains(this.Q.get(i))){
                texto+="\t\t\t"+"<final/>"+"\n";
            }
            texto+="\t\t"+"</state>"+"\n";
        }

        /*
         * <!--Lista de transiciones.-->
		<transition>
			<from>1</from>
			<to>0</to>
			<read>b</read>
		</transition>
         */
        texto+="\t\t"+"<!--Lista de transiciones.-->"+"\n";
        for(Transicion tran : this.delta){
            texto+="\t\t"+"<transition>"+"\n";
            texto+="\t\t\t"+"<from>"+this.Q.lastIndexOf(tran.getDesde())+"</from>"+"\n";
            texto+="\t\t\t"+"<to>"+this.Q.lastIndexOf(tran.getHacia())+"</to>"+"\n";
            texto+="\t\t\t"+"<read>"+tran.getTransicion()+"</read>"+"\n";
            texto+="\t\t"+"</transition>"+"\n";
        }

        texto+="\t</automaton>"+"\n";
        texto+="</structure>"+"\n";
        File TextFile = new File("/home/martin/Desktop/prueba.jff");
        FileWriter TextOut;
        try {
            TextOut = new FileWriter(TextFile, false);
            TextOut.write(texto);
            TextOut.close();
        } catch (Exception ex) {

        }

    }
    /***********************************************
    * Método: Generar_JFLAP
    * Uso: Genera el código en un xml que JFLAP entiende
    *      como un autómata y lo dibuja
    * @return void
    ***********************************************/
    public void Generar_DOT(String nombre_archivo){
        /*digraph finite_state_machine {
	rankdir=LR;
	size="8,5"
	node [shape=doublecircle]; q0 q3 q4 q8;
	node [shape=circle];
	q0 -> q2 [label="a"];
	q0 -> q1 [label="a"];}
         */
        String texto = "digraph finite_state_machine {\n";

        texto +="\trankdir=LR;"+"\n";
        int numero = 8;
        numero +=(int)(this.Q.size()/(10));
        texto +="\tsize=\""+numero+",5\""+"\n";
        texto +="\tnode [shape=doublecircle];";
        //listar estados de aceptación...
        for(int i=0;i<this.F.size();i++){
            texto+=" "+F.get(i).getNombre();
        }
        //
        texto+=";"+"\n";
        texto +="\tnode [shape=circle];"+"\n";
	//transiciones
        for(int i=0;i<this.delta.size();i++){
            texto+="\t"+delta.get(i).DOT_String()+"\n";
        }
        texto+="}";

        String archivo = Main.path_to_save+Main.contador_automatas_creados+"/Automatas/";
        boolean booleano = new File(archivo).mkdir();
        archivo+=nombre_archivo+Main.contador_automatas_creados+".dot";

        File TextFile = new File(archivo);
        FileWriter TextOut;
        try {
            TextOut = new FileWriter(TextFile, false);
            TextOut.write(texto);
            TextOut.close();
        } catch (Exception ex) {

        }

        //ejecutar comando: dot -Tjpg -O digraph2.dot
        String comando = "dot -Tjpg -O "+archivo;
        try
        {
           /* directorio/ejecutable es el path del ejecutable y un nombre */
           Process p = Runtime.getRuntime().exec (comando);
        }
        catch (Exception e)
        {
           /* Se lanza una excepción si no se encuentra en ejecutable o el fichero no es ejecutable. */
        }
        //this.Print(comando);

    }

    /***********************************************
    * Método: Imprimir_Todo_AFD
    * Uso: Imprime todo el AFD
    * @return void
    ***********************************************/
    public void Imprimir_Todo_AF(String nombre_archivo, String linea, long tiempo) {

        this.ordenar_transiciones();
        this.ordenar_estados();
        String texto="";

        texto+=("*************************************************************\n");
        texto+=(linea+"\n*************************************************************\n");
        texto+=("Tiempo de creación del autómanta: "+(tiempo)+"ns = "+(((double)(tiempo)/(double)100000))+"ms\n");
        texto+=("Genera un lenguaje a partir de la regex: "+Main.regex+" -> "+this.regex);
        texto+=("\n*************************************************************\n");
        texto+=("Lista de símbolos: "+this.Sigma.toString()+"\n");
        texto+=("Estado inicial (q0): "+this.q0.toString()+"\n");
        texto+=("Lista de estados (Q): "+this.Q.toString()+"\n");
        texto+=("Lista de aceptación (F): "+this.F.toString()+"\n");
        texto+=("Transiciones:"+"\n");
        for(int i=0;i<this.delta.size();i++){
            texto+=("\t* "+this.delta.get(i).toString()+"\n");
        }
        texto+=("\n*************************************************************");

        if(Main.imprimir_automatas_en_pantalla){
            this.Print(texto);
        }

        String path = Main.path_to_save+Main.contador_automatas_creados;
        
        boolean prueba = new File(path).mkdir();

        File TextFile = new File(path+"/"+nombre_archivo+Main.contador_automatas_creados+".txt");

        //System.out.println(prueba);

        FileWriter TextOut;
        try {
            TextOut = new FileWriter(TextFile, false);
            TextOut.write(texto);
            TextOut.close();
        } catch (Exception ex) {
            System.out.println("Error al guardar el archivo");
        }

        this.Generar_DOT(nombre_archivo);


    }

}